/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.sql.rowset.spi;

import java.util.logging.*;
import java.util.*;

import java.sql.*;
import javax.sql.*;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileNotFoundException;

import javax.naming.*;

/**
 * The Service Provider Interface (SPI) mechanism that generates <code>SyncProvider</code>
 * instances to be used by disconnected <code>RowSet</code> objects. 
 * The <code>SyncProvider</code> instances in turn provide the
 * <code>javax.sql.RowSetReader</code> object the <code>RowSet</code> object
 * needs to populate itself with data and the 
 * <code>javax.sql.RowSetWriter</code> object it needs to
 * propagate changes to its
 * data back to the underlying data source. 
 * <P>
 * Because the methods in the <code>SyncFactory</code> class are all static,
 * there is only one <code>SyncFactory</code> object
 * per Java VM at any one time. This ensures that there is a single source from which a
 * <code>RowSet</code> implementation can obtain its <code>SyncProvider</code> 
 * implementation. 
 * <p>
 * <h3>1.0 Overview</h3>
 * The <code>SyncFactory</code> class provides an internal registry of available 
 * synchronization provider implementations (<code>SyncProvider</code> objects). 
 * This registry may be queried to determine which
 * synchronization providers are available. 
 * The following line of code gets an enumeration of the providers currently registered.
 * <PRE>
 *     java.util.Enumeration e = SyncFactory.getRegisteredProviders();
 * </PRE>
 * All standard <code>RowSet</code> implementations must provide at least two providers: 
 * <UL>
 *  <LI>an optimistic provider for use with a <code>CachedRowSet</code> implementation 
 *     or an implementation derived from it
 *  <LI>an XML provider, which is used for reading and writing XML, such as with
 *       <code>WebRowSet</code> objects
 * </UL> 
 * Note that the JDBC RowSet Implementations include the <code>SyncProvider</code>
 * implemtations <code>RIOptimisticProvider</code> and <code>RIXmlProvider</code>,
 * which satisfy this requirement.
 * <P>
 * The <code>SyncFactory</code> class provides accessor methods to assist 
 * applications in determining which synchronization providers are currently
 * registered with the <code>SyncFactory</code>.
 * <p>
 * Other methods let <code>RowSet</code> persistence providers be
 * registered or de-registered with the factory mechanism. This 
 * allows additional synchronization provider implementations to be made 
 * available to <code>RowSet</code> objects at run time.       
 * <p>
 * Applications can apply a degree of filtering to determine the level of 
 * synchronization that a <code>SyncProvider</code> implementation offers. 
 * The following criteria determine whether a provider is 
 * made available to a <code>RowSet</code> object:
 * <ol>
 * <li>If a particular provider is specified by a <code>RowSet</code> object, and 
 * the <code>SyncFactory</code> does not contain a reference to this provider,
 * a <code>SyncFactoryException</code> is thrown stating that the synchronization 
 * provider could not be found.
 * <p>
 * <li>If a <code>RowSet</code> implementation is instantiated with a specified 
 * provider and the specified provider has been properly registered, the 
 * requested provider is supplied. Otherwise a <code>SyncFactoryException</code>
 * is thrown.
 * <p>
 * <li>If a <code>RowSet</code> object does not specify a 
 * <code>SyncProvider</code> implementation and no additional 
 * <code>SyncProvider</code> implementations are available, the reference 
 * implementation providers are supplied.
 * </ol>
 * <h3>2.0 Registering <code>SyncProvider</code> Implementations</h3>
 * <p>
 * Both vendors and developers can register <code>SyncProvider</code> 
 * implementations using one of the following mechanisms.
 * <ul>
 * <LI><B>Using the command line</B><BR>
 * The name of the provider is supplied on the command line, which will add 
 * the provider to the system properties.
 * For example:
 * <PRE> 
 *    -Drowset.provider.classname=com.fred.providers.HighAvailabilityProvider
 * </PRE> 
 * <li><b>Using the Standard Properties File</b><BR> 
 * The reference implementation is targeted 
 * to ship with J2SE 1.5, which will include an additional resource file 
 * that may be edited by hand. Here is an example of the properties file
 * included in the reference implementation:
 * <PRE>
 *   #Default JDBC RowSet sync providers listing
 *   #
 *
 *   # Optimistic synchronization provider
 *   rowset.provider.classname.0=com.sun.rowset.providers.RIOptimisticProvider
 *   rowset.provider.vendor.0=Sun Microsystems Inc
 *   rowset.provider.version.0=1.0
 *   
 *   # XML Provider using standard XML schema
 *   rowset.provider.classname.1=com.sun.rowset.providers.RIXMLProvider
 *   rowset.provider.vendor.1=Sun Microsystems Inc.
 *   rowset.provider.version.1=1.0
 * </PRE>
 * The <code>SyncFactory</code> checks this file and registers the 
 * <code>SyncProvider</code> implementations that it contains. A
 * developer or vendor can add other implementations to this file.
 * For example, here is a possible addition:
 * <PRE>
 *     rowset.provider.classname.2=com.fred.providers.HighAvailabilityProvider
 *     rowset.provider.vendor.2=Fred, Inc.
 *     rowset.provider.version.2=1.0
 * </PRE>
 * <p>
 * <li><b>Using a JNDI Context</b><BR>
 * Available providers can be registered on a JNDI
 * context, and the <code>SyncFactory</code> will attempt to load 
 * <code>SyncProvider</code> implementations from that JNDI context.
 * For example, the following code fragment registers a provider implementation
 * on a JNDI context.  This is something a deployer would normally do. In this
 * example, <code>MyProvider</code> is being registered on a CosNaming
 * namespace, which is the namespace used by J2EE resources.
 * <PRE>
 *    import javax.naming.*;
 *    
 *    Hashtable svrEnv = new  Hashtable();
 *    srvEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
 *
 *    Context ctx = new InitialContext(svrEnv);
 *    com.fred.providers.MyProvider = new MyProvider();
 *    ctx.rebind("providers/MyProvider", syncProvider);
 * </PRE>
 * </ul> 
 * Next, an application will register the JNDI context with the 
 * <code>SyncFactory</code> instance.  This allows the <code>SyncFactory</code>
 * to browse within the JNDI context looking for <code>SyncProvider</code>
 * implementations. 
 * <PRE>
 *    Hashtable appEnv = new Hashtable();
 *    appEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
 *    appEnv.put(Context.PROVIDER_URL, "iiop://hostname/providers");
 *    Context ctx = new InitialContext(appEnv);
 *
 *    SyncFactory.registerJNDIContext(ctx);
 * </PRE>
 * If a <code>RowSet</code> object attempts to obtain a <code>MyProvider</code>
 * object, the <code>SyncFactory</code> will try to locate it. First it searches
 * for it in the system properties, then it looks in the resource files, and
 * finally it checks the JNDI context that has been set. The <code>SyncFactory</code>
 * instance verifies that the requested provider is a valid extension of the
 * <code>SyncProvider</code> abstract class and then gives it to the 
 * <code>RowSet</code> object. In the following code fragment, a new
 * <code>CachedRowSet</code> object is created and initialized with
 * <i>env</i>, which contains the binding to <code>MyProvider</code>.
 * <PRE>
 *    Hashtable env = new Hashtable();
 *    env.put(SyncFactory.ROWSET_SYNC_PROVIDER, "com.fred.providers.MyProvider");
 *    CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl(env); 
 * </PRE>
 * Further details on these mechanisms are available in the 
 * <code>javax.sql.rowset.spi</code> package specification.
 *
 * @author  Jonathan Bruce
 * @see javax.sql.rowset.spi.SyncProvider
 * @see javax.sql.rowset.spi.SyncFactoryException
 */
public class SyncFactory {
    
    /* 
     * The variable that represents the singleton instance
     * of the <code>SyncFactory</code> class.
     */
    private static SyncFactory syncFactory = null;
 
    /**
     * Creates a new <code>SyncFactory</code> object, which is the singleton
     * instance.
     * Having a private constructor guarantees that no more than
     * one <code>SyncProvider</code> object can exist at a time.
     */
    private SyncFactory() {};
    
    /**
     * The standard property-id for a synchronization provider implementation
     * name.
     */
    public static String ROWSET_SYNC_PROVIDER = 
	"rowset.provider.classname";
    /**
     * The standard property-id for a synchronization provider implementation 
     * vendor name.
     */ 
    public static String ROWSET_SYNC_VENDOR = 
	"rowset.provider.vendor";

    /**
     * The standard property-id for a synchronization provider implementation
     * version tag. 
     */
    public static String ROWSET_SYNC_PROVIDER_VERSION = 
	"rowset.provider.version";

    /**
     * The standard resource file name.
     */
    private static String ROWSET_PROPERTIES = "rowset.properties";        
    
    /** 
     * The RI Optimistic Provider.     
     */     
    private static String default_provider = 
        "com.sun.rowset.providers.RIOptimisticProvider";

    /**
     *  Permission required to invoke setJNDIContext and setLogger
     */
    private static final SQLPermission SET_SYNCFACTORY_PERMISSION =
            new SQLPermission("setSyncFactory");
    /**

     * The initial JNDI context where <code>SyncProvider</code> implementations can
     * be stored and from which they can be invoked.
     */
    private static Context ic;

    /**
     * The <code>Logger</code> object to be used by the <code>SyncFactory</code>.
     */
    private static Logger rsLogger;

    /**
     *
     */
    private static Level rsLevel; 
           
    /**
     * The registry of available <code>SyncProvider</code> implementations.
     * See section 2.0 of the class comment for <code>SyncFactory</code> for an
     * explanation of how a provider can be added to this registry.
     */
    private static Hashtable implementations;            

    /**
     * Internal sync object used to maintain the SPI as a singleton
     */
    private static Object logSync = new Object();       
    
    /**
     * Internal PrintWriter field for logging facility
     */ 
    private static java.io.PrintWriter logWriter = null;

    /**
     * Adds the the given synchronization provider to the factory register. Guidelines
     * are provided in the <code>SyncProvider</code> specification for the 
     * required naming conventions for <code>SyncProvider</code> 
     * implementations.
     * <p>
     * Synchronization providers bound to a JNDI context can be 
     * registered by binding a SyncProvider instance to a JNDI namespace.     
     * <ul>
     * <pre>
     * SyncProvider p = new MySyncProvider();
     * InitialContext ic = new InitialContext();
     * ic.bind ("jdbc/rowset/MySyncProvider", p);
     * </pre>
     * </ul>
     * Furthermore, an initial JNDI context should be set with the 
     * <code>SyncFactory</code> using the <code>setJNDIContext</code> method.
     * The <code>SyncFactory</code> leverages this context to search for 
     * available <code>SyncProvider</code> objects bound to the JNDI
     * context and its child nodes.
     *      
     * @param providerID A <code>String</code> object with the unique ID of the 
     *             synchronization provider being registered
     * @throws SyncFactoryException if an attempt is made to supply an empty
     *         or null provider name
     * @see #setJNDIContext
     */
    public static synchronized void registerProvider(String providerID) 
        throws SyncFactoryException {
        
        ProviderImpl impl = new ProviderImpl();
        impl.setClassname(providerID);
        initMapIfNecessary();
        implementations.put(providerID, impl);
        
    }

    /**
     * Returns the <code>SyncFactory</code> singleton.
     * 
     * @return the <code>SyncFactory</code> instance
     */			
     public static SyncFactory getSyncFactory() {
         
         // This method uses the Singleton Design Pattern
         // with Double-Checked Locking Pattern for
         // 1. Creating single instance of the SyncFactory
         // 2. Make the class thread safe, so that at one time
         //    only one thread enters the synchronized block
         //    to instantiate. 
         
         // if syncFactory object is already there
         // don't go into synchronized block and return
         // that object.
         // else go into synchronized block
         
         if (syncFactory == null) {
	     synchronized (SyncFactory.class) {
	        if (syncFactory == null) {
	            syncFactory = new SyncFactory();
	        } //end if   
	     } //end synchronized block    
         } //end if
         return syncFactory;
     }

    /**
     * Removes the designated currently registered synchronization provider from the 
     * Factory SPI register.
     *
     * @param providerID The unique-id of the synchronization provider
     * @throws SyncFactoryException If an attempt is made to
     * unregister a SyncProvider implementation that was not registered.
     */
    public static synchronized void unregisterProvider(String providerID) 
        throws SyncFactoryException {           
        initMapIfNecessary();    
        if (implementations.containsKey(providerID)) {
            implementations.remove(providerID);
        }
    }

    private static String colon = ":";
    private static String strFileSep = "/";    
    
    private static synchronized void initMapIfNecessary() throws SyncFactoryException {                
        
        // Local implementation class names and keys from Properties
        // file, translate names into Class objects using Class.forName
        // and store mappings
        Properties properties = new Properties();                            
                                
        if (implementations == null) {
            implementations = new Hashtable();                                    
                        
            try {                        
                                
                // check if user is supplying his Synchronisation Provider 
                // Implementation  if not use Sun's implementation.                                      
                // properties.load(new FileInputStream(ROWSET_PROPERTIES));
                 
                // The rowset.properties needs to be in jdk/jre/lib when 
                // integrated with jdk.
                // else it should be picked from -D option from command line.

                // -Drowset.properties will add to standard properties. Similar
                // keys will over-write

                /*
                 * Dependent on application
                 */
                String strRowsetProperties = System.getProperty("rowset.properties");
                if (strRowsetProperties != null) {
                    // Load user's implementation of SyncProvider 
                    // here. -Drowset.properties=/abc/def/pqr.txt
                    ROWSET_PROPERTIES = strRowsetProperties; 
                    properties.load(new FileInputStream(ROWSET_PROPERTIES));
                    parseProperties(properties);
                } 
                 
                /*
                 * Always available 
                 */
                ROWSET_PROPERTIES = "javax" + strFileSep + "sql" + 
                    strFileSep + "rowset" + strFileSep + 
                    "rowset.properties";
                // properties.load(
                //                ClassLoader.getSystemResourceAsStream(ROWSET_PROPERTIES));
                
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                
                properties.load(cl.getResourceAsStream(ROWSET_PROPERTIES));
                parseProperties(properties);

                // removed else, has properties should sum together

            } catch (FileNotFoundException e) {
                throw new SyncFactoryException("Cannot locate properties file: " + e);
            } catch (IOException e) {
                throw new SyncFactoryException("IOException: " + e);
            }
                                      
            /*
             * Now deal with -Drowset.provider.classname
             * load additional properties from -D command line
             */
            properties.clear();
            String providerImpls = System.getProperty(ROWSET_SYNC_PROVIDER);      

            if (providerImpls != null) {
                int i = 0;
                if (providerImpls.indexOf(colon) > 0) {
                    StringTokenizer tokenizer = new StringTokenizer(providerImpls, colon);
                    while (tokenizer.hasMoreElements()) {
                        properties.put(ROWSET_SYNC_PROVIDER + "." + i, tokenizer.nextToken());
                        i++;
                    }
                } else {
                    properties.put(ROWSET_SYNC_PROVIDER, providerImpls);
                }
                parseProperties(properties);
            }
        }        
    }    
    
    /**
     * The internal boolean switch that indicates whether a JNDI 
     * context has been established or not.
     */
    private static boolean jndiCtxEstablished = false;
    
    /**
     * The internal debug switch.
     */
    private static boolean debug = false;
    
    /**
     * Internal registry count for the number of providers contained in the 
     * registry.
     */
    private static int providerImplIndex = 0;

    /**
     * Internal handler for all standard property parsing. Parses standard 
     * ROWSET properties and stores lazy references into the the internal registry. 
     */
    private static void parseProperties(Properties p) {

        ProviderImpl impl = null;
        String key = null;
        String[] propertyNames = null;               
        
        for (Enumeration e = p.propertyNames(); e.hasMoreElements();) {

            String str = (String) e.nextElement();
                               
            int w = str.length();

            if (str.startsWith(SyncFactory.ROWSET_SYNC_PROVIDER)) {
                
                impl = new ProviderImpl();
                impl.setIndex(providerImplIndex++);

                if (w == (SyncFactory.ROWSET_SYNC_PROVIDER).length()) {
                    // no property index has been set.
                    propertyNames = getPropertyNames(false);
                } else {
                    // property index has been set. 
                    propertyNames = getPropertyNames(true, str.substring(w - 1));
                }

                key = p.getProperty(propertyNames[0]);                
                impl.setClassname(key);
                impl.setVendor(p.getProperty(propertyNames[1]));
                impl.setVersion(p.getProperty(propertyNames[2]));
                implementations.put(key, impl);                                
            }
        }            
    }

    /**
     * Used by the parseProperties methods to disassemble each property tuple.
     */
    private static String[] getPropertyNames(boolean append) {
        return getPropertyNames(append, null);
    }
    
    /**
     * Disassembles each property and its associated value. Also handles 
     * overloaded property names that contain indexes.
     */
    private static String[] getPropertyNames(boolean append, 
                                             String propertyIndex) {
        String dot = ".";
        String[] propertyNames = 
            new String[]{SyncFactory.ROWSET_SYNC_PROVIDER,
                         SyncFactory.ROWSET_SYNC_VENDOR,
                         SyncFactory.ROWSET_SYNC_PROVIDER_VERSION};
        if (append) {
            for (int i = 0; i < propertyNames.length; i++) {
                propertyNames[i] = propertyNames[i] + 
                    dot + 
                    propertyIndex;
            }
            return propertyNames;
        } else {
            return propertyNames;
        }
    }

    /**
     * Internal debug method that outputs the registry contents.
     */
    private static void showImpl(ProviderImpl impl) {
        System.out.println("Provider implementation:");
        System.out.println("Classname: " + impl.getClassname());
        System.out.println("Vendor: " + impl.getVendor());
        System.out.println("Version: " + impl.getVersion());
        System.out.println("Impl index: " + impl.getIndex());
    }

    /**
     * Returns the <code>SyncProvider</code> instance identified by <i>providerID</i>.
     *
     * @param providerID the unique identifier of the provider
     * @return a <code>SyncProvider</code> implementation
     * @throws SyncFactoryException If the SyncProvider cannot be found or 
     * some error was encountered when trying to invoke this provider.
     */    
    public static SyncProvider getInstance(String providerID) 
	throws SyncFactoryException {
        initMapIfNecessary(); // populate HashTable
        initJNDIContext();    // check JNDI context for any additional bindings
        
        ProviderImpl impl = (ProviderImpl) implementations.get(providerID);
        
        if (impl == null) {                            
            // Requested SyncProvider is unavailable. Return default provider.            
            return new com.sun.rowset.providers.RIOptimisticProvider();
        }
        
        // Attempt to invoke classname from registered SyncProvider list
        Class c = null;                
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();           
            
            /**
             * The SyncProvider implementation of the user will be in 
             * the classpath. We need to find the ClassLoader which loads 
             * this SyncFactory and try to laod the SyncProvider class from 
             * there.
             **/
	    
	    c = Class.forName(providerID, true, cl);
	    	
            if (c != null) {                
                return (SyncProvider) c.newInstance();
            } else {                
                return new com.sun.rowset.providers.RIOptimisticProvider();              
            }                
            
        } catch (IllegalAccessException e) {
            throw new SyncFactoryException("IllegalAccessException: " + e.getMessage());            
        } catch (InstantiationException e) {
            throw new SyncFactoryException("InstantiationException: " + e.getMessage());            
        } catch (ClassNotFoundException e) {
            throw new SyncFactoryException("ClassNotFoundException: " + e.getMessage());
        }                       
    }
    /**
     * Returns an Enumeration of currently registered synchronization 
     * providers.  A <code>RowSet</code> implementation may use any provider in
     * the enumeration as its <code>SyncProvider</code> object.
     * <p>
     * At a minimum, the reference synchronization provider allowing 
     * RowSet content data to be stored using a JDBC driver should be
     * possible.
     *
     * @return Enumeration  A enumeration of available synchronization 
     * providers that are registered with this Factory
     */    
    public static Enumeration<SyncProvider> getRegisteredProviders() 
        throws SyncFactoryException {
        initMapIfNecessary();
        // return a collection of classnames
        // of type SyncProvider
        return implementations.elements();
    }
        
    /**
     * Sets the logging object to be used by the <code>SyncProvider</code> 
     * implementation provided by the <code>SyncFactory</code>. All
     * <code>SyncProvider</code> implementations can log their events to 
     * this object and the application can retrieve a handle to this 
     * object using the <code>getLogger</code> method.
     * <p>
     * This method checks to see that there is an {@code SQLPermission}
     * object  which grants the permission {@code setSyncFactory}
     * before allowing the method to succeed.  If a
     * {@code SecurityManager} exists and its
     * {@code checkPermission} method denies calling {@code setLogger},
     * this method throws a
     * {@code java.lang.SecurityException}.
     * 
     * @param logger A Logger object instance
     * @throws java.lang.SecurityException if a security manager exists and its
     *   {@code checkPermission} method denies calling {@code setLogger}
     * @see SecurityManager#checkPermission
     */
    public static void setLogger(Logger logger) {

        SecurityManager sec = System.getSecurityManager();
        if (sec != null) {
            sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
        }

        rsLogger = logger;
    }

    /**
     * Sets the logging object that is used by <code>SyncProvider</code>
     * implementations provided by the <code>SyncFactory</code> SPI. All
     * <code>SyncProvider</code> implementations can log their events  
     * to this object and the application can retrieve a handle to this
     * object using the <code>getLogger</code> method.
     * <p>
     * This method checks to see that there is an {@code SQLPermission}
     * object  which grants the permission {@code setSyncFactory}
     * before allowing the method to succeed.  If a
     * {@code SecurityManager} exists and its
     * {@code checkPermission} method denies calling {@code setLogger},
     * this method throws a
     * {@code java.lang.SecurityException}.
     *
     * @param logger a Logger object instance
     * @param level a Level object instance indicating the degree of logging
     * required
     * @throws java.lang.SecurityException if a security manager exists and its
     *   {@code checkPermission} method denies calling {@code setLogger}
     * @see SecurityManager#checkPermission
     */
    public static void setLogger(Logger logger, Level level) {
	// singleton 
       
        SecurityManager sec = System.getSecurityManager();
        if (sec != null) {
            sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
        }

	rsLogger = logger;
	rsLogger.setLevel(level);
    }

    /**
     * Returns the logging object for applications to retrieve 
     * synchronization events posted by SyncProvider implementations.
     *
     * @throws SyncFactoryException if no logging object has been set.
     */
    public static Logger getLogger() throws SyncFactoryException {
	// only one logger per session
	if (rsLogger == null) {
	   throw new SyncFactoryException("(SyncFactory) : No logger has been set");
	}
	return rsLogger;
    }

   /**
    * Sets the initial JNDI context from which SyncProvider implementations 
    * can be retrieved from a JNDI namespace
    * <p>
    *  This method checks to see that there is an {@code SQLPermission}
    * object  which grants the permission {@code setSyncFactory}
    * before allowing the method to succeed.  If a
    * {@code SecurityManager} exists and its
    * {@code checkPermission} method denies calling {@code setJNDIContext}, 
    * this method throws a 
    * {@code java.lang.SecurityException}.
    *
    * @param ctx a valid JNDI context
    * @throws SyncFactoryException if the supplied JNDI context is null
    * @throws java.lang.SecurityException if a security manager exists and its
    *  {@code checkPermission} method denies calling {@code setJNDIContext}
    * @see SecurityManager#checkPermission
    */
    public static void setJNDIContext(javax.naming.Context ctx) 
	throws SyncFactoryException {
       SecurityManager sec = System.getSecurityManager();
       if (sec != null) {
           sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
       }

	if (ctx == null) {
	    throw new SyncFactoryException("Invalid JNDI context supplied");
	}
	ic = ctx;        
        jndiCtxEstablished = true;
   }
    
    /**
     * Controls JNDI context intialization.
     *
     * @throws SyncFactoryException if an error occurs parsing the JNDI context
     */    
    private static void initJNDIContext() throws SyncFactoryException {        
                        
        if (jndiCtxEstablished && (ic != null) && (lazyJNDICtxRefresh == false)) {
            try {                                
                parseProperties(parseJNDIContext());
                lazyJNDICtxRefresh = true; // touch JNDI namespace once.
            } catch (NamingException e) {
		e.printStackTrace();
                throw new SyncFactoryException("SPI: NamingException: " + e.getExplanation());
            } catch (Exception e) {
		e.printStackTrace();
	throw new SyncFactoryException("SPI: Exception: " + e.getMessage());
	    }
        }           
    }
    /**
     * Internal switch indicating whether the JNDI namespace should be re-read.
     */
    private static boolean lazyJNDICtxRefresh = false;
        
    /**
     * Parses the set JNDI Context and passes bindings to the enumerateBindings
     * method when complete.
     */
    private static Properties parseJNDIContext() throws NamingException {
                
        NamingEnumeration bindings = ic.listBindings("");
        Properties properties = new Properties();        
        
        // Hunt one level below context for available SyncProvider objects                            
	enumerateBindings(bindings, properties);

        return properties;
    }

    /**
     * Scans each binding on JNDI context and determines if any binding is an 
     * instance of SyncProvider, if so, add this to the registry and continue to
     * scan the current context using a re-entrant call to this method until all
     * bindings have been enumerated.     
     */
    private static void enumerateBindings(NamingEnumeration bindings, 
	Properties properties) throws NamingException {

	boolean syncProviderObj = false; // move to parameters ?

	try {
	    Binding bd = null;
	    Object elementObj = null;
	    String element = null;
	    while (bindings.hasMore()) {
	        bd = (Binding) bindings.next();
	        element = bd.getName();
	        elementObj = bd.getObject();

	        if (!(ic.lookup(element) instanceof Context)) {
		     // skip directories/sub-contexts
	             if (ic.lookup(element) instanceof SyncProvider) {
			syncProviderObj = true;
		     }
		}

		if (syncProviderObj) {
		    SyncProvider sync = (SyncProvider) elementObj;
		    properties.put(SyncFactory.ROWSET_SYNC_PROVIDER,
			sync.getProviderID());
		    syncProviderObj = false; // reset
		}

            }
	} catch (javax.naming.NotContextException e) {
	    bindings.next();
	    // Re-entrant call into method
	    enumerateBindings(bindings, properties);
	}
    }
}

/**
  * Internal class that defines the lazy reference construct for each registered 
  * SyncProvider implementation.
  */
class ProviderImpl extends SyncProvider {
      private String className = null;
      private String vendorName = null;
      private String ver = null;
      private int index;

      public void setClassname(String classname) {
          className = classname;
      }

      public String getClassname() {
          return className;
      }

      public void setVendor(String vendor) {
          vendorName = vendor;
      }

      public String getVendor() {
          return vendorName;
      }

      public void setVersion(String providerVer) {
          ver = providerVer;
      }
          
      public String getVersion() {
          return ver;
      }

      public void setIndex(int i) {
          index = i;
      }
          
      public int getIndex() {
          return index;
      }
      
      public int getDataSourceLock() throws SyncProviderException {
      
         int dsLock = 0;
          try {
             dsLock = SyncFactory.getInstance(className).getDataSourceLock();
          } catch(SyncFactoryException sfEx) {
             throw new SyncProviderException(sfEx.getMessage());
          }
           
          return dsLock;
      }
      
      public int getProviderGrade() {
      
         int grade = 0;
         
         try {
            grade =  SyncFactory.getInstance(className).getProviderGrade();
         } catch (SyncFactoryException sfEx) {
             // 
         }
         
         return grade;
      }
      
      public String getProviderID() {
          return className;
      }
      
      /*
      public javax.sql.RowSetInternal getRowSetInternal() {  
        try 
         {          
            return SyncFactory.getInstance(className).getRowSetInternal();
         } catch(SyncFactoryException sfEx) {
             // 
         }
      }
      */
      
      public javax.sql.RowSetReader getRowSetReader() {
      
      RowSetReader rsReader = null;
      
      try { 
         rsReader = SyncFactory.getInstance(className).getRowSetReader();
      } catch (SyncFactoryException sfEx) {
             // 
      }  
       
       return rsReader;
       
      }
      
      public javax.sql.RowSetWriter getRowSetWriter() {
      
      RowSetWriter rsWriter = null;
      try { 
          rsWriter =  SyncFactory.getInstance(className).getRowSetWriter();
      } catch (SyncFactoryException sfEx) {
             // 
      }
         
         return rsWriter;
      }
      public void setDataSourceLock(int param) 
      throws SyncProviderException { 
       
       try {  
            SyncFactory.getInstance(className).setDataSourceLock(param);
       } catch (SyncFactoryException sfEx) {
            throw new SyncProviderException(sfEx.getMessage());
       }   
      }
      
      public int supportsUpdatableView() {
      
      int view = 0;
      
      try { 
         view = SyncFactory.getInstance(className).supportsUpdatableView();
      } catch (SyncFactoryException sfEx) {
             // 
      }
       
       return view;  
     } 

}
